package com.zhihao.domain.strategy.service.armory.algorithm.impl;

import com.zhihao.domain.strategy.model.entity.StrategyAwardEntity;
import com.zhihao.domain.strategy.service.armory.algorithm.AbstractAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @author Jackson
 * @create 2025/4/7 9:50
 * @description 万分位等高分位的节省空间算法-循环、二分、多线程
 */
@Slf4j
@Component("oLogNAlgorithm")
public class OLogNAlgorithm extends AbstractAlgorithm {

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;  // 线程池

    /**
     * 预热活动概率值
     * 如概率值为；3、4、2、9，存储为 [1~3]、[4~7]、[8~9]、[10~18]，抽奖时，for循环匹配。
     *
     * @param key 活动ID/策略ID
     * @param strategyAwardEntities 抽奖策略/轮盘对应的奖品实体类
     * @param rateRange 最小奖品概率分位对应的10次幂值
     */
    @Override
    public void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities, BigDecimal rateRange) {
        log.info("抽奖算法 OLog(n) 装配 key:{}", key);
        int from = 1; // 区间左值
        int to = 0; // 区间后值(后一个奖品概率区间的左值)

        Map<Map<Integer, Integer>, Integer> table = new HashMap<>();
        for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
            Integer awardId = strategyAward.getAwardId();
            BigDecimal awardRate = strategyAward.getAwardRate();
            to = to + rateRange.multiply(awardRate).intValue();
            // 每个奖品的区间map对象 k是左值,v是右值
            Map<Integer, Integer> ft = new HashMap<>();
            ft.put(from, to);
            table.put(ft, awardId);
            // 循环：下一个奖品的左值是上一个奖品的右值+1
            from = to + 1;
        }

        // 最终的局部变量to值就是所有奖品位置的总和(即：总概率*rateRange-10次幂，table后面的抽奖随机数生成不会有未命中)
        repository.storeStrategyAwardSearchRateTable(key, to, table);
    }


    /**
     * 执行抽奖逻辑
     * @param key 策略id
     * @return 奖品ID
     */
    @Override
    public Integer dispatchAlgorithm(String key) {
        // 获取策略id对应的抽奖随机数生成范围(即所有奖品实际的概率范围)
        int rateRange = repository.getRateRange(key);
        Map<Map<String, Integer>, Integer> table = repository.getMap(key);
        // 策略/轮盘配置的奖品个数：小于等于8 for循环、小于等于16 二分查找、更多检索走多线程
        if (table.size() <= 8) {
            log.info("抽奖算法 OLog(n) 抽奖计算（循环） key:{}", key);
            return forSearch(secureRandom.nextInt(rateRange), table);
        } else if (table.size() <= 16) {
            log.info("抽奖算法 OLog(n) 抽奖计算（二分） key:{}", key);
            return binarySearch(secureRandom.nextInt(rateRange), table);
        } else {
            log.info("抽奖算法 OLog(n) 抽奖计算（多线程） key:{}", key);
            return threadSearch(secureRandom.nextInt(rateRange), table);
        }
    }

    /**
     * 循环
     * @param rateKey 生成的随机数-代表抽奖的结果落在了哪个奖品上
     * @param table 策略奖品查找表
     * @return awardId-奖品id
     */
    private Integer forSearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
        Integer awardId = null;
        // map -> entry -> k,v（redis的key会默认转变integer->string）
        for (Map.Entry<Map<String, Integer>, Integer> entry : table.entrySet()) {
            Map<String, Integer> rangeMap = entry.getKey();

            // 这个range按道理说就是range.size()=1
            for (Map.Entry<String, Integer> range : rangeMap.entrySet()) {
                int start = Integer.parseInt(range.getKey());
                int end = range.getValue();

                if (rateKey >= start && rateKey <= end) {
                    awardId = entry.getValue();
                    break;
                }
            }
            // break只能退出当前的循环，所以需要这个循环退出最外层循环，否则找到后也会遍历后续无效区间
            if (awardId != null) {
                break;
            }
        }

        return awardId;
    }

    /**
     * 区间也可以二分查找：（前提：有序且区间不重叠）
     * 和普通的二分查找类似，只有比较每个节点值的时候不一样而已。单一数值直接比较；区间则需要比较区间左右边界
     * @param rateKey 生成的随机数-代表抽奖的结果落在了哪个奖品上
     * @param table 策略奖品查找表
     * @return awardId-奖品id
     */
    private Integer binarySearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
        // 二分查找：1.有序列表
        List<Map.Entry<Map<String, Integer>, Integer>> entries = new ArrayList<>(table.entrySet());
        // 根据“区间左值”排序
        entries.sort(Comparator.comparingInt(e -> Integer.parseInt(e.getKey().keySet().iterator().next())));

        int left = 0;
        int right = entries.size() - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            Map.Entry<Map<String, Integer>, Integer> entry = entries.get(mid);
            Map<String, Integer> rangeMap = entry.getKey();
            Map.Entry<String, Integer> range = rangeMap.entrySet().iterator().next();

            int start = Integer.parseInt(range.getKey());
            int end = range.getValue();

            if (rateKey < start) {
                right = mid - 1;
            } else if (rateKey > end) {
                left = mid + 1;
            } else {
                return entry.getValue();
            }
        }

        return null;
    }

    /**
     * 多线程并行查找
     *  核心优化点：
     *      1 利用 CompletableFuture 实现异步并发查询，每个区间检查任务独立运行，提高查询效率。
     *      2 使用线程池 threadPoolExecutor 控制并发度，避免无限制创建线程。
     *      3 返回第一个匹配的结果，一旦找到目标区间，立即终止其他任务的等待（但不会取消已提交的任务）。
     *      适用于大规模区间查询，利用多核 CPU 加速。
     * @param rateKey 生成的随机数-代表抽奖的结果落在了哪个奖品上
     * @param table 策略奖品查找表
     * @return awardId-奖品id
     */
    private Integer threadSearch(int rateKey, Map<Map<String, Integer>, Integer> table) {
        // 1.创建并发任务列表
        /*
            CompletableFuture.supplyAsync() 异步执行一个任务，并返回 CompletableFuture 对象。
                    会立即提交任务到线程池异步执行，但具体执行时机取决于线程池的调度策略
         */
        List<CompletableFuture<Map.Entry<Map<String, Integer>, Integer>>> futures = table.entrySet().stream()
                // entry: Map.Entry< Map<String, Integer>, Integer> >
                .map(entry -> CompletableFuture.supplyAsync(() -> {
                    // rangeMap: Map<String, Integer>
                    Map<String, Integer> rangeMap = entry.getKey();
                    // rangeEntry: Map.Entry<String, Integer> size=1
                    for (Map.Entry<String, Integer> rangeEntry : rangeMap.entrySet()) {
                        // String
                        int start = Integer.parseInt(rangeEntry.getKey());
                        // Integer
                        int end = rangeEntry.getValue();
                        if (rateKey >= start && rateKey <= end) {
                            return entry;
                        }
                    }
                    return null;
                }, threadPoolExecutor))
                .collect(Collectors.toList());

        /*
            List.toArray() 的两种用法:
                1.无参版本：返回 Object[]，可能需强制类型转换。   问题：强制类型转换可能导致 ClassCastException（虽然编译时不报错）。
                2.带参版本：传入一个类型匹配的空数组，返回正确类型的数组。  优点：直接返回 CompletableFuture[]，无需强转，类型安全
         */
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        try {
            // 阻塞主线程 直到所有任务完成
            allFutures.join();
            //  future.getNow():返回第一个匹配的结果
            for (CompletableFuture<Map.Entry<Map<String, Integer>, Integer>> future : futures) {
                // 非阻塞地获取 Future 的当前结果
                //  如果任务已完成，返回计算结果。  如果未完成，返回 defaultValue（你代码中用 null）。
                Map.Entry<Map<String, Integer>, Integer> result = future.getNow(null);
                if (result != null) {
                    return result.getValue();
                }
            }
        } catch (CompletionException e) {
            e.printStackTrace();
        }

        return null;
    }



}
